import { test, expect, Page } from '../../../playwright';
import { buildCommonLocators } from './locators';

type SandboxMode = 'safe' | 'developer';

/**
 * Close all collections
 * @param page - The page object
 * @returns void
 */
const closeAllCollections = async (page) => {
  await test.step('Close all collections', async () => {
    const numberOfCollections = await page.locator('[data-testid="collections"] .collection-name').count();

    for (let i = 0; i < numberOfCollections; i++) {
      await page.locator('[data-testid="collections"] .collection-name').first().locator('.collection-actions').click();
      await page.locator('.dropdown-item').getByText('Remove').click();
      // Wait for the remove collection modal to be visible
      await page.locator('.bruno-modal-header-title', { hasText: 'Remove Collection' }).waitFor({ state: 'visible' });
      await page.locator('.bruno-modal-footer .submit').click();
      // Wait for the remove collection modal to be hidden
      await page.locator('.bruno-modal-header-title', { hasText: 'Remove Collection' }).waitFor({ state: 'hidden' });
    }

    // Wait until no collections are left open (check sidebar only)
    await expect(page.getByTestId('collections').locator('.collection-name')).toHaveCount(0);
  });
};

/**
 * Open a collection from the sidebar and accept the JavaScript Sandbox modal
 * @param page - The page object
 * @param collectionName - The name of the collection to open
 * @param sandboxMode - The mode to accept the sandbox modal
 * @returns void
 */
const openCollectionAndAcceptSandbox = async (page, collectionName: string, sandboxMode: 'safe' | 'developer' = 'safe') => {
  await test.step(`Open collection "${collectionName}" and accept sandbox "${sandboxMode}" mode`, async () => {
    await page.locator('#sidebar-collection-name').filter({ hasText: collectionName }).click();

    const sandboxModal = page
      .locator('.bruno-modal-card')
      .filter({ has: page.locator('.bruno-modal-header-title', { hasText: 'JavaScript Sandbox' }) });

    const modeLabel = sandboxMode === 'safe' ? 'Safe Mode' : 'Developer Mode';
    await sandboxModal.getByLabel(modeLabel).check();
    await sandboxModal.locator('.bruno-modal-footer .submit').click();
    await sandboxModal.waitFor({ state: 'detached' });
  });
};

type CreateCollectionOptions = {
  openWithSandboxMode?: 'safe' | 'developer';
};

/**
 * Create a collection
 * @param page - The page object
 * @param collectionName - The name of the collection to create
 * @param collectionLocation - The location of the collection to create (eg)
 * @param options - The options for creating the collection
 *
 * @returns void
 */
const createCollection = async (page, collectionName: string, collectionLocation: string, options: CreateCollectionOptions = {}) => {
  await test.step(`Create collection "${collectionName}"`, async () => {
    await page.locator('.plus-icon-button').click();
    await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();

    const createCollectionModal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Collection' });

    await createCollectionModal.getByLabel('Name').fill(collectionName);
    const locationInput = createCollectionModal.getByLabel('Location');
    if (await locationInput.isVisible()) {
      await locationInput.fill(collectionLocation);
    }
    await createCollectionModal.getByRole('button', { name: 'Create', exact: true }).click();

    await createCollectionModal.waitFor({ state: 'detached' });

    if (options.openWithSandboxMode != undefined) {
      await openCollectionAndAcceptSandbox(page, collectionName, options.openWithSandboxMode);
    }
  });
};

type CreateRequestOptions = {
  url?: string;
  inFolder?: boolean;
};

/**
 * Create a request in a collection or folder
 * @param page - The page object
 * @param requestName - The name of the request to create
 * @param parentName - The name of the collection or folder
 * @param options - Optional settings (url, inFolder)
 * @returns void
 */
const createRequest = async (
  page: Page,
  requestName: string,
  parentName: string,
  options: CreateRequestOptions = {}
) => {
  const { url, inFolder = false } = options;
  const parentType = inFolder ? 'folder' : 'collection';

  await test.step(`Create request "${requestName}" in ${parentType} "${parentName}"`, async () => {
    const locators = buildCommonLocators(page);

    if (inFolder) {
      await locators.sidebar.folder(parentName).hover();
      await locators.actions.collectionItemActions(parentName).click();
    } else {
      await locators.sidebar.collection(parentName).hover();
      await locators.actions.collectionActions(parentName).click();
    }

    await locators.dropdown.item('New Request').click();
    await page.getByPlaceholder('Request Name').fill(requestName);

    if (url) {
      await page.locator('#new-request-url .CodeMirror').click();
      await page.keyboard.type(url);
    }

    await locators.modal.button('Create').click();

    if (inFolder) {
      await expect(locators.sidebar.folderRequest(parentName, requestName)).toBeVisible();
    } else {
      await expect(locators.sidebar.request(requestName)).toBeVisible();
    }
  });
};

/**
 * Delete a request from a collection
 * @param page - The page object
 * @param requestName - The name of the request to delete
 * @param collectionName - The name of the collection
 * @returns void
 */
const deleteRequest = async (page, requestName: string, collectionName: string) => {
  await test.step(`Delete request "${requestName}" from collection "${collectionName}"`, async () => {
    const locators = buildCommonLocators(page);

    // Click on the collection first to open it if it's closed
    await locators.sidebar.collection(collectionName).click();

    // Find the request within the collection's context
    // Use the collection container (.collection-name) scoped to sidebar to scope the search
    const collectionContainer = page.getByTestId('collections').locator('.collection-name').filter({ hasText: collectionName });
    const collectionWrapper = collectionContainer.locator('..');
    const request = collectionWrapper.locator('.collection-item-name').filter({ hasText: requestName });

    await request.locator('.menu-icon').click();
    await locators.dropdown.item('Delete').click();
    await locators.modal.button('Delete').click();
    await expect(request).not.toBeVisible();
  });
};

/**
 * Import a collection from a file
 * @param page - The page object
 * @param filePath - The path to the collection file to import
 * @param collectionLocation - The directory where the collection will be saved
 * @param options - Optional settings for import
 * @returns void
 */
type ImportCollectionOptions = {
  expectedCollectionName?: string;
  openWithSandboxMode?: SandboxMode;
};

const importCollection = async (
  page: Page,
  filePath: string,
  collectionLocation: string,
  options: ImportCollectionOptions = {}
) => {
  await test.step(`Import collection from "${filePath}"`, async () => {
    const locators = buildCommonLocators(page);

    await page.locator('.plus-icon-button').click();
    await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();

    // Wait for import modal
    const importModal = page.getByRole('dialog');
    await importModal.waitFor({ state: 'visible' });
    await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');

    // Set the file
    await page.setInputFiles('input[type="file"]', filePath);

    // Wait for location modal to appear
    const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
    await locationModal.waitFor({ state: 'visible', timeout: 10000 });
    await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');

    // Verify expected collection name if provided
    if (options.expectedCollectionName) {
      await expect(locationModal.getByText(options.expectedCollectionName)).toBeVisible();
    }

    // Set location and import
    await page.locator('#collection-location').fill(collectionLocation);
    await locationModal.getByRole('button', { name: 'Import' }).click();

    // Wait for collection to appear in sidebar
    if (options.expectedCollectionName) {
      await expect(
        page.locator('#sidebar-collection-name').filter({ hasText: options.expectedCollectionName })
      ).toBeVisible();

      // Configure sandbox mode if requested
      if (options.openWithSandboxMode) {
        await openCollectionAndAcceptSandbox(page, options.expectedCollectionName, options.openWithSandboxMode);
      }
    }
  });
};

/**
 * Remove a specific collection from the sidebar
 * @param page - The page object
 * @param collectionName - The name of the collection to remove
 * @returns void
 */
const removeCollection = async (page: Page, collectionName: string) => {
  await test.step(`Remove collection "${collectionName}"`, async () => {
    const locators = buildCommonLocators(page);
    const collectionRow = page.locator('.collection-name').filter({
      has: page.locator('#sidebar-collection-name', { hasText: collectionName })
    });

    await collectionRow.hover();
    await collectionRow.locator('.collection-actions .icon').click();
    await locators.dropdown.item('Remove').click();

    // Wait for and confirm modal
    await locators.modal.title('Remove Collection').waitFor({ state: 'visible' });
    await locators.modal.button('Remove').click();
    await locators.modal.title('Remove Collection').waitFor({ state: 'hidden' });

    // Verify collection is removed
    await expect(
      page.locator('#sidebar-collection-name').filter({ hasText: collectionName })
    ).not.toBeVisible();
  });
};

/**
 * Create a folder inside a collection or another folder
 * @param page - The page object
 * @param folderName - The name of the folder to create
 * @param parentName - The name of the parent collection or folder
 * @param isCollection - Whether the parent is a collection (true) or folder (false)
 * @returns void
 */
const createFolder = async (
  page: Page,
  folderName: string,
  parentName: string,
  isCollection: boolean = true
) => {
  await test.step(`Create folder "${folderName}" in "${parentName}"`, async () => {
    const locators = buildCommonLocators(page);

    if (isCollection) {
      await locators.sidebar.collection(parentName).hover();
      await locators.actions.collectionActions(parentName).click();
    } else {
      await locators.sidebar.folder(parentName).hover();
      await locators.actions.collectionItemActions(parentName).click();
    }

    await locators.dropdown.item('New Folder').click();
    await page.getByPlaceholder('Folder Name').fill(folderName);
    await locators.modal.button('Create').click();
    await expect(locators.sidebar.folder(folderName)).toBeVisible();
  });
};

type EnvironmentType = 'collection' | 'global';

/**
 * Open the environment selector panel
 * @param page - The page object
 * @param type - The type of environment tab to select
 * @returns void
 */
const openEnvironmentSelector = async (page: Page, type: EnvironmentType = 'collection') => {
  await test.step(`Open ${type} environment selector`, async () => {
    const locators = buildCommonLocators(page);

    await locators.environment.selector().click();

    if (type === 'global') {
      await locators.environment.globalTab().click();
      await expect(locators.environment.globalTab()).toHaveClass(/active/);
    } else {
      await expect(locators.environment.collectionTab()).toHaveClass(/active/);
    }
  });
};

/**
 * Create a new environment
 * @param page - The page object
 * @param environmentName - The name of the environment
 * @param type - The type of environment (collection or global)
 * @returns void
 */
const createEnvironment = async (
  page: Page,
  environmentName: string,
  type: EnvironmentType = 'collection'
) => {
  await test.step(`Create ${type} environment "${environmentName}"`, async () => {
    await openEnvironmentSelector(page, type);

    await page.locator('button[id="create-env"]').click();

    const nameInput = page.locator('input[name="name"]');
    await expect(nameInput).toBeVisible();
    await nameInput.fill(environmentName);
    await page.getByRole('button', { name: 'Create' }).click();
  });
};

type EnvironmentVariable = {
  name: string;
  value: string;
  isSecret?: boolean;
};

/**
 * Add an environment variable to the currently open environment
 * @param page - The page object
 * @param variable - The variable to add (name, value, and optional secret flag)
 * @param index - The index of the variable (0-based)
 * @returns void
 */
const addEnvironmentVariable = async (
  page: Page,
  variable: EnvironmentVariable,
  index: number
) => {
  await test.step(`Add environment variable "${variable.name}"`, async () => {
    const addButton = page.locator('button[data-testid="add-variable"]');
    await addButton.waitFor({ state: 'visible' });
    await addButton.click();

    // Wait for the new row to be added and the name input to be visible
    const nameInput = page.locator(`input[name="${index}.name"]`);
    await nameInput.waitFor({ state: 'visible' });
    await nameInput.fill(variable.name);

    // Wait for the CodeMirror editor in the row to be ready
    const variableRow = page.locator('tr').filter({ has: page.locator(`input[name="${index}.name"]`) });
    const codeMirror = variableRow.locator('.CodeMirror');
    await codeMirror.waitFor({ state: 'visible' });
    await codeMirror.click();
    await page.keyboard.type(variable.value);

    if (variable.isSecret) {
      const secretCheckbox = page.locator(`input[name="${index}.secret"]`);
      await secretCheckbox.waitFor({ state: 'visible' });
      await secretCheckbox.check();
    }
  });
};

/**
 * Add multiple environment variables to the currently open environment
 * @param page - The page object
 * @param variables - Array of variables to add
 * @returns void
 */
const addEnvironmentVariables = async (page: Page, variables: EnvironmentVariable[]) => {
  await test.step(`Add ${variables.length} environment variables`, async () => {
    for (let i = 0; i < variables.length; i++) {
      await addEnvironmentVariable(page, variables[i], i);
    }
  });
};

/**
 * Save the current environment settings
 * @param page - The page object
 * @returns void
 */
const saveEnvironment = async (page: Page) => {
  await test.step('Save environment', async () => {
    await page.getByRole('button', { name: 'Save' }).click();
  });
};

/**
 * Close the environment modal/panel
 * @param page - The page object
 * @returns void
 */
const closeEnvironmentPanel = async (page: Page) => {
  await test.step('Close environment panel', async () => {
    await page.getByText('×').click();
  });
};

/**
 * Select an environment from the dropdown
 * @param page - The page object
 * @param environmentName - The name of the environment to select
 * @param type - The type of environment (collection or global)
 * @returns void
 */
const selectEnvironment = async (
  page: Page,
  environmentName: string,
  type: EnvironmentType = 'collection'
) => {
  await test.step(`Select ${type} environment "${environmentName}"`, async () => {
    const locators = buildCommonLocators(page);

    await locators.environment.selector().click();

    if (type === 'global') {
      await locators.environment.globalTab().click();
    }

    await locators.environment.envOption(environmentName).click();

    // Verify selection
    await expect(page.locator('.current-environment')).toContainText(environmentName);
  });
};

/**
 * Send the current request and wait for response
 * @param page - The page object
 * @param expectedStatusCode - Optional expected status code to wait for
 * @param timeout - Timeout in milliseconds (default: 30000)
 * @returns void
 */
const sendRequest = async (
  page: Page,
  expectedStatusCode?: number | string,
  timeout: number = 30000
) => {
  await test.step('Send request', async () => {
    await page.getByTestId('send-arrow-icon').click();
    await page.getByTestId('response-status-code').waitFor({ state: 'visible', timeout });

    if (expectedStatusCode !== undefined) {
      await expect(page.getByTestId('response-status-code')).toContainText(
        String(expectedStatusCode),
        { timeout }
      );
    }
  });
};

/**
 * Open a request by clicking on it in the sidebar
 * @param page - The page object
 * @param requestName - The name of the request to open
 * @returns void
 */
const openRequest = async (page: Page, requestName: string) => {
  await test.step(`Open request "${requestName}"`, async () => {
    const locators = buildCommonLocators(page);
    await locators.sidebar.request(requestName).click();
    await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
  });
};

/**
 * Open a request within a folder
 * @param page - The page object
 * @param folderName - The name of the folder
 * @param requestName - The name of the request
 * @returns void
 */
const openFolderRequest = async (page: Page, folderName: string, requestName: string) => {
  await test.step(`Open request "${requestName}" in folder "${folderName}"`, async () => {
    const locators = buildCommonLocators(page);
    await locators.sidebar.folderRequest(folderName, requestName).click();
    await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
  });
};

/**
 * Get the response body text
 * @param page - The page object
 * @returns The response body text
 */
const getResponseBody = async (page: Page): Promise<string> => {
  return await page.locator('.response-pane').innerText();
};

/**
 * Verify response contains specific text
 * @param page - The page object
 * @param texts - Array of texts to verify in the response
 * @returns void
 */
const expectResponseContains = async (page: Page, texts: string[]) => {
  await test.step('Verify response content', async () => {
    const responsePane = page.locator('.response-pane');
    for (const text of texts) {
      await expect(responsePane).toContainText(text);
    }
  });
};

export {
  closeAllCollections,
  openCollectionAndAcceptSandbox,
  createCollection,
  createRequest,
  deleteRequest,
  importCollection,
  removeCollection,
  createFolder,
  openEnvironmentSelector,
  createEnvironment,
  addEnvironmentVariable,
  addEnvironmentVariables,
  saveEnvironment,
  closeEnvironmentPanel,
  selectEnvironment,
  sendRequest,
  openRequest,
  openFolderRequest,
  getResponseBody,
  expectResponseContains
};

export type { SandboxMode, EnvironmentType, EnvironmentVariable, CreateCollectionOptions, ImportCollectionOptions, CreateRequestOptions };
